package com.sanweibook.jhook.biz;

import com.google.common.collect.Lists;
import com.sanweibook.jhook.common.Constants;
import com.sanweibook.jhook.common.util.JhookStringUtils;
import com.sanweibook.jhook.dal.dao.CrudDao;
import com.sanweibook.jhook.dal.model.BaseModel;
import com.sanweibook.jhook.dal.page.Page;
import com.sanweibook.jhook.dal.page.PageRequest;
import com.sanweibook.jhook.dal.util.SnowflakeIdWorker;
import com.sanweibook.jhook.redis.core.RedisClientTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.SerializationUtils;

import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 * Created by twg on 17/3/9.
 */
@Slf4j
public abstract class CrudService<D extends CrudDao<T>, T extends BaseModel> extends BaseService {
    @Autowired
    private D crudDao;
    @Autowired
    private RedisClientTemplate redisClientTemplate;

    /**
     * 根据属性，删除记录
     *
     * @param t
     * @return
     */
    protected int delete(final T t, final String cacheKey) {
        Assert.notNull( t, "not null t required" );
        int result = crudDao.delete( t );
        removeCacheKeys( cacheKey );
        return result;
    }


    /**
     * 根据id，删除记录
     *
     * @param id
     * @return
     */
    protected int deleteById(final Long id, final String cacheKey) {
        Assert.notNull( id, "not null id required" );
        int result = crudDao.deleteById( id );
        removeCacheKeys( cacheKey );
        return result;
    }

    /**
     * 根据ids，批量删除记录
     *
     * @param ids
     * @return
     */
    protected int deleteByIds(final List<Long> ids, final String cacheKey) {
        Assert.notEmpty( ids, "ids must not empty" );
        int result = crudDao.deleteByIds( ids );
        removeCacheKeys( cacheKey );
        return result;
    }

    /**
     * 根据属性，获取单条记录
     *
     * @param t
     * @param cacheKey 缓存key(sysuer:get)
     * @return
     */
    protected T get(final T t, final String cacheKey) {
        Assert.notNull( t, "not null t required" );
        if (JhookStringUtils.isBlank( cacheKey )) {
            return crudDao.get( t );
        }
        byte[] cachekey = super.createCachKey( t, cacheKey );
        byte[] v = (byte[]) redisClientTemplate.get( cachekey );
        if (null == v) {
            T r = crudDao.get( t );
            if (r != null) {
                redisClientTemplate.set( cachekey, SerializationUtils.serialize( r ) );
            }
            return r;
        }
        return (T) SerializationUtils.deserialize( v );
    }

    /**
     * 根据id，获取单条记录
     *
     * @param id
     * @param cacheKey 缓存key(sysuer:getbyid)
     * @return
     */
    protected T getById(final Long id, final String cacheKey) {
        Assert.notNull( id, "not null id required" );
        if (JhookStringUtils.isBlank( cacheKey )) {
            return crudDao.getById( id );
        }
        byte[] cachekey = super.createCachKey( id, cacheKey );
        byte[] v = (byte[]) redisClientTemplate.get( cachekey );
        if (null == v) {
            T r = crudDao.getById( id );
            if (r != null) {
                redisClientTemplate.set( cachekey, SerializationUtils.serialize( r ) );
            }
            return r;
        }
        return (T) SerializationUtils.deserialize( v );
    }

    /**
     * 根据属性，获取记录集总数
     *
     * @param t
     * @param cacheKey 缓存key(sysuer:querycount)
     * @return
     */
    protected int queryCount(final T t, final String cacheKey) {
        Assert.notNull( t, "not null t required" );
        if (JhookStringUtils.isBlank( cacheKey )) {
            return crudDao.queryCount( t );
        }
        byte[] cachekey = super.createCachKey( t, cacheKey );
        byte[] v = (byte[]) redisClientTemplate.get( cachekey );
        if (null == v) {
            int r = crudDao.queryCount( t );
            redisClientTemplate.set( cachekey, SerializationUtils.serialize( r ) );
            return r;
        }
        return (Integer) SerializationUtils.deserialize( v );
    }

    /**
     * 根据属性，获取全部记录集
     *
     * @param t
     * @param cacheKey 缓存key(sysuer:queryall)
     * @return
     */
    protected List<T> queryAll(final T t, final String cacheKey) {
        Assert.notNull( t, "not null t required" );
        if (JhookStringUtils.isBlank( cacheKey )) {
            return crudDao.queryAll( t );
        }
        byte[] cachekey = super.createCachKey( t, cacheKey );
        byte[] v = (byte[]) redisClientTemplate.get( cachekey );
        if (null == v) {
            List<T> r = crudDao.queryAll( t );
            redisClientTemplate.set( cachekey, SerializationUtils.serialize( r ) );
            return r;
        }
        return (List<T>) SerializationUtils.deserialize( v );
    }

    /**
     * 根据ids，获取记录集（多条）
     *
     * @param ids
     * @param cacheKey 缓存key(sysuer:querybyids)
     * @return
     */
    protected List<T> queryByIds(final List<Long> ids, final String cacheKey) {
        Assert.notEmpty( ids, "ids must not empty" );
        if (JhookStringUtils.isBlank( cacheKey )) {
            return getResultByIds( ids );
        }
        byte[] cachekey = super.createCachKey( ids, cacheKey );
        byte[] v = (byte[]) redisClientTemplate.get( cachekey );
        if (null == v) {
            List<T> r = getResultByIds( ids );
            redisClientTemplate.set( cachekey, SerializationUtils.serialize( r ) );
            return r;
        }
        return (List<T>) SerializationUtils.deserialize( v );
    }

    /**
     * 根据属性，分页获取记录集
     *
     * @param t
     * @return
     */
    protected Page<T> queryByPage(final T t) {
        Assert.notNull( t, "not null t required" );
        PageRequest<T> pageRequest = (PageRequest<T>) t.getPage();
        int count = crudDao.queryCount( t );
        if (count == 0) {
            return pageRequest;
        }
        pageRequest.setCount( count );
        pageRequest.setContents( crudDao.queryByPage( t ) );
        return pageRequest;
    }

    /**
     * 根据属性，添加记录
     *
     * @param t
     * @return
     */
    @Transactional
    protected int insert(final T t, final String cacheKey) {
        Assert.notNull( t, "not null t required" );
        t.setId( SnowflakeIdWorker.getInstance().nextId() );
        t.setIsDeleted( 0 );
        t.setGmtCreate( new Date() );
        t.setGmtModified( t.getGmtCreate() );
        int result = crudDao.insert( t );
        removeCacheKeys( cacheKey );
        return result;
    }

    /**
     * 根据属性，更新记录
     *
     * @param t
     * @return
     */
    protected int update(final T t, final String cacheKey) {
        Assert.notNull( t, "not null t required" );
        t.setGmtModified( new Date() );
        int result = crudDao.update( t );
        removeCacheKeys( cacheKey );
        return result;
    }

    /**
     * 根据ids，批量添加记录
     *
     * @param ts
     * @return
     */
    protected int batchInsert(final List<T> ts, final String cacheKey) {
        Assert.notEmpty( ts, "ts must not empty" );
        for (T t : ts) {
            t.setId( SnowflakeIdWorker.getInstance().nextId() );
            t.setIsDeleted( 0 );
            t.setGmtCreate( new Date() );
            t.setGmtModified( t.getGmtCreate() );
        }
        int total = ts.size();
        int size = (int) Math.ceil( (double) ts.size() / (double) BATCH_SIZE );
        for (int i = 0; i < size; i++) {
            if (i + 1 != size) {
                crudDao.batchInsert( ts.subList( i * BATCH_SIZE, i * BATCH_SIZE + BATCH_SIZE ) );
            } else {
                crudDao.batchInsert( ts.subList( i * BATCH_SIZE, total ) );
            }
        }
        removeCacheKeys( cacheKey );
        return total;
    }


    private void removeCacheKeys(String cacheKey) {
        if (JhookStringUtils.isBlank( cacheKey )) {
            return;
        }
        Set<byte[]> b = redisClientTemplate.keys( JhookStringUtils.stringToByte( cacheKey ) );
        if (b.isEmpty()) {
            return;
        }
        redisClientTemplate.delKeys( b.toArray( new byte[][]{} ) );
    }

    private List<T> getResultByIds(List<Long> ids) {
        List<T> result = Lists.newArrayList();
        List<CompletableFuture<List<T>>> futures = Lists.newArrayList();
        List<List<Long>> _ids = Lists.partition( ids, Constants.SUBLIST_SIZE );
        for (List<Long> id : _ids) {
            futures.add( CompletableFuture.supplyAsync( () -> {
                return crudDao.queryByIds( id );
            } ) );
        }
        futures.forEach( listCompletableFuture -> {
            try {
                result.addAll( listCompletableFuture.get() );
            } catch (InterruptedException e) {
                log.error( "GetResultByIds is error.", e );
            } catch (ExecutionException e) {
                log.error( "GetResultByIds is error.", e );
            }
        } );
        return result;
    }
}
